﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Media.Media3D;
using System.Collections.Generic;

namespace EffectLibrary {
    public class MatrixHelp : DependencyObject {
        double[,] fMatrix;
        public const int rlen = 4;
        public const int clen = 4;
        public Matrix3DProjection pMatrix3DProjection { get; private set; }
        public MatrixHelp pUnit { get { this.SetToUnit(); return this; } }
        public MatrixHelp pZero { get { this.SetToZero(); return this; } }
        public MatrixHelp pOnes { get { this.SetToOnes(); return this; } }
        public MatrixHelp() {
            fMatrix = new double[rlen,clen];
            this.pMatrix3DProjection = new Matrix3DProjection();
        }
        #region basic operator
        protected double this[int i,int j] {
            get { return this.fMatrix[i,j]; }
            set { this.fMatrix[i,j] = value; }
        }
        protected IEnumerable<Tuple<double,byte,byte>> Ergodic(ErgodicType et,byte point = 0) {
            if (point < 0 || point > 3) {
                throw new Exception("指定行列数超出范围，范围在0到4之间");
            }
            switch (et) {
                case ErgodicType.all:
                for (byte i = 0 ; i < rlen ; i++) {
                    for (byte j = 0 ; j < clen ; j++) {
                        yield return Tuple.Create(this.fMatrix[i,j],i,j);
                    }
                }
                break;
                case ErgodicType.column:
                for (byte i = 0 ; i < rlen ; i++) {
                    yield return Tuple.Create(this.fMatrix[i,point],i,point);
                }
                break;
                case ErgodicType.row:
                for (byte j = 0 ; j < clen ; j++) {
                    yield return Tuple.Create(this.fMatrix[point,j],point,j);
                }
                break;
                default:
                break;
            }
        }
        //
        protected void SetToZero() {
            for (int i = 0 ; i < rlen ; i++) {
                for (int j = 0 ; j < clen ; j++) {
                    fMatrix[i,j] = 0;
                }
            }
        }
        protected void SetToOnes() {
            for (int i = 0 ; i < rlen ; i++) {
                for (int j = 0 ; j < clen ; j++) {
                    fMatrix[i,j] = 1;
                }
            }
        }
        protected void SetToUnit() {
            for (int i = 0 ; i < rlen ; i++) {
                for (int j = 0 ; j < clen ; j++) {
                    if (i == j) {
                        fMatrix[i,j] = 1;
                        continue;
                    }
                    fMatrix[i,j] = 0;
                }
            }
        }
        protected void SetToTranspose() {
            double[,] tempmatrix = new double[rlen,clen];
            for (int i = 0 ; i < rlen ; i++) {
                for (int j = 0 ; j < clen ; j++) {
                    tempmatrix[j,i] = fMatrix[i,j];
                }
            }
            fMatrix = null;
            fMatrix = tempmatrix;
        }
        //

        public static implicit operator Matrix(MatrixHelp d3) {
            Matrix r = default(Matrix);
            if (d3 != null && d3.fMatrix != null)
                r = new Matrix(
                    d3.fMatrix[0,0],d3.fMatrix[0,1],d3.fMatrix[1,0],d3.fMatrix[1,1],
                    d3.fMatrix[3,0],d3.fMatrix[3,1]
                    );
            return r;
        }

        public static implicit operator Matrix3D(MatrixHelp d3) {
            Matrix3D r = default(Matrix3D);
            if (d3 != null && d3.fMatrix != null)
                r = new Matrix3D(
                    d3.fMatrix[0,0],d3.fMatrix[0,1],d3.fMatrix[0,2],d3.fMatrix[0,3],
                    d3.fMatrix[1,0],d3.fMatrix[1,1],d3.fMatrix[1,2],d3.fMatrix[1,3],
                    d3.fMatrix[2,0],d3.fMatrix[2,1],d3.fMatrix[2,2],d3.fMatrix[2,3],
                    d3.fMatrix[3,0],d3.fMatrix[3,1],d3.fMatrix[3,2],d3.fMatrix[3,3]
                    );
            return r;
        }
        public static implicit operator MatrixHelp(Matrix3D m3) {
            MatrixHelp r = default(MatrixHelp);
            if (m3 != default(Matrix3D)) {
                r = new MatrixHelp {
                    fMatrix = new double[MatrixHelp.rlen,MatrixHelp.clen],
                };
                r.fMatrix[0,0] = m3.M11;
                r.fMatrix[0,1] = m3.M12;
                r.fMatrix[0,2] = m3.M13;
                r.fMatrix[0,3] = m3.M14;

                r.fMatrix[1,0] = m3.M21;
                r.fMatrix[1,1] = m3.M22;
                r.fMatrix[1,2] = m3.M23;
                r.fMatrix[1,3] = m3.M24;

                r.fMatrix[2,0] = m3.M31;
                r.fMatrix[2,1] = m3.M32;
                r.fMatrix[2,2] = m3.M33;
                r.fMatrix[2,3] = m3.M34;

                r.fMatrix[3,0] = m3.OffsetX;
                r.fMatrix[3,1] = m3.OffsetY;
                r.fMatrix[3,2] = m3.OffsetZ;
                r.fMatrix[3,3] = m3.M44;
            }
            return r;
        }
        public static MatrixHelp operator +(MatrixHelp m1,MatrixHelp m2) {
            for (int i = 0 ; i < MatrixHelp.rlen ; i++) {
                for (int j = 0 ; j < MatrixHelp.clen ; j++) {
                    m1.fMatrix[i,j] += m2.fMatrix[i,j];
                }
            }
            return m1;
        }
        public static MatrixHelp operator -(MatrixHelp m1,MatrixHelp m2) {
            for (int i = 0 ; i < MatrixHelp.rlen ; i++) {
                for (int j = 0 ; j < MatrixHelp.clen ; j++) {
                    m1.fMatrix[i,j] -= m2.fMatrix[i,j];
                }
            }
            return m1;
        }
        public static MatrixHelp operator *(MatrixHelp m1,MatrixHelp m2) {
            MatrixHelp d3 = new MatrixHelp();
            for (int i = 0 ; i < 4 ; i++) {
                for (int j = 0 ; j < 4 ; j++) {
                    d3[i,j] = m1[i,0] * m2[0,j] + m1[i,1] * m2[1,j] + m1[i,2] * m2[2,j] + m1[i,3] * m2[3,j];
                }
            }
            return d3;
        }
        public static MatrixHelp operator /(MatrixHelp m1,MatrixHelp m2) {
            MatrixHelp d3 = default(MatrixHelp);
            return d3;
        }
        #endregion
        #region operator
        public MatrixHelp Interlace(double s,double t,AxisType at) {
            this.SetToUnit();
            switch (at) {
                case AxisType.x:
                this.fMatrix[2,0] = s;
                this.fMatrix[2,1] = t;
                break;
                case AxisType.z:
                this.fMatrix[1,0] = s;
                this.fMatrix[1,2] = t;
                break;
                case AxisType.y:
                this.fMatrix[0,1] = s;
                this.fMatrix[0,2] = t;
                break;
                default:
                break;
            }
            return this;
        }
        public void ResetMatrix() {
            this.fMatrix = new double[rlen,clen];
        }
        public MatrixHelp Scale(double scalefactor) {
            this.SetToZero();
            this.fMatrix[0,0] = scalefactor;
            this.fMatrix[1,1] = scalefactor;
            this.fMatrix[2,2] = scalefactor;
            this.fMatrix[3,3] = 1;
            return this;
        }
        public MatrixHelp Scale(double dx,double dy,double dz) {
            this.SetToZero();
            this.fMatrix[0,0] = dx;
            this.fMatrix[1,1] = dy;
            this.fMatrix[2,2] = dz;
            this.fMatrix[3,3] = 1;
            return this;
        }
        public MatrixHelp Spin(double angle,AxisType st) {
            this.SetToUnit();
            switch (st) {
                case AxisType.x:
                this.fMatrix[1,1] = Math.Cos(angle);
                this.fMatrix[1,2] = -Math.Sin(angle);
                this.fMatrix[2,1] = Math.Sin(angle);
                this.fMatrix[2,2] = Math.Cos(angle);
                break;
                case AxisType.y:
                this.fMatrix[0,0] = Math.Cos(angle);
                this.fMatrix[0,2] = Math.Sin(angle);
                this.fMatrix[2,0] = -Math.Sin(angle);
                this.fMatrix[2,2] = Math.Cos(angle);
                break;
                case AxisType.z:
                this.fMatrix[0,0] = Math.Cos(angle);
                this.fMatrix[0,1] = -Math.Sin(angle);
                this.fMatrix[1,0] = Math.Sin(angle);
                this.fMatrix[1,1] = Math.Cos(angle);
                break;
                default:
                break;
            }
            return this;
        }
        public MatrixHelp Translation(double x,double y,double z) {
            this.SetToUnit();
            this.fMatrix[3,0] = x;
            this.fMatrix[3,1] = y;
            this.fMatrix[3,2] = z;
            return this;
        }
        public void Update() {
            this.pMatrix3DProjection.ProjectionMatrix = this;
        }
        public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged(string pn) {
            if (this.PropertyChanged != null) {
                this.PropertyChanged.Invoke(this,new System.ComponentModel.PropertyChangedEventArgs(pn));
            }
        }
        #endregion
        //
        #region extends
        /// <summary>
        /// 数乘
        /// </summary>
        /// <param name="v"></param>
        public void NumberBy(double v) {
            for (int i = 0 ; i < rlen ; i++) {
                for (int j = 0 ; j < clen ; j++) {
                    this.fMatrix[i,j] *= v;
                }
            }
        }
        /// <summary>
        /// 数乘某行或列
        /// </summary>
        /// <param name="v"></param>
        public void NumberBy(double v,byte rc,NumberByType nbt) {
            switch (nbt) {
                case NumberByType.row:
                for (int i = 0 ; i < clen ; i++) {
                    this[rc,i] *= v;
                }
                break;
                case NumberByType.column:
                for (int i = 0 ; i < rlen ; i++) {
                    this[i,rc] *= v;
                }
                break;
                default:
                break;
            }
        }
        #endregion
    }
    public enum AxisType {
        x, y, z
    }
    public enum ErgodicType {
        all, column, row
    }
    public enum NumberByType {
        row, column
    }
}